﻿/* Copyright (c) 2008, 2009, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    partial class x86_64_Assembler
    {
        const int HIGH_REG_START = 8;

        public static x86_64_gpr Rax { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rax }; } }
        public static x86_64_gpr Rbx { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rbx }; } }
        public static x86_64_gpr Rcx { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rcx }; } }
        public static x86_64_gpr Rdx { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rdx }; } }
        public static x86_64_gpr Rsi { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rsi }; } }
        public static x86_64_gpr Rdi { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rdi }; } }
        public static x86_64_gpr Rsp { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rsp }; } }
        public static x86_64_gpr Rbp { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.rbp }; } }
        public static x86_64_gpr R8 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r8 }; } }
        public static x86_64_gpr R9 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r9 }; } }
        public static x86_64_gpr R10 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r10 }; } }
        public static x86_64_gpr R11 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r11 }; } }
        public static x86_64_gpr R12 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r12 }; } }
        public static x86_64_gpr R13 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r13 }; } }
        public static x86_64_gpr R14 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r14 }; } }
        public static x86_64_gpr R15 { get { return new x86_64_gpr { reg = x86_64_gpr.RegId.r15 }; } }

        public static x86_64_xmm Xmm0 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm0 }; } }
        public static x86_64_xmm Xmm1 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm1 }; } }
        public static x86_64_xmm Xmm2 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm2 }; } }
        public static x86_64_xmm Xmm3 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm3 }; } }
        public static x86_64_xmm Xmm4 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm4 }; } }
        public static x86_64_xmm Xmm5 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm5 }; } }
        public static x86_64_xmm Xmm6 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm6 }; } }
        public static x86_64_xmm Xmm7 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm7 }; } }
        public static x86_64_xmm Xmm8 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm8 }; } }
        public static x86_64_xmm Xmm9 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm9 }; } }
        public static x86_64_xmm Xmm10 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm10 }; } }
        public static x86_64_xmm Xmm11 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm11 }; } }
        public static x86_64_xmm Xmm12 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm12 }; } }
        public static x86_64_xmm Xmm13 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm13 }; } }
        public static x86_64_xmm Xmm14 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm14 }; } }
        public static x86_64_xmm Xmm15 { get { return new x86_64_xmm { xmm = x86_64_xmm.XmmId.xmm15 }; } }

        hloc_constraint CRax { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rax }; } }
        hloc_constraint CRbx { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rbx }; } }
        hloc_constraint CRcx { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rcx }; } }
        hloc_constraint CRdx { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rdx }; } }
        hloc_constraint CRsi { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rsi }; } }
        hloc_constraint CRdi { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rdi }; } }
        hloc_constraint CRsp { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rsp }; } }
        hloc_constraint CRbp { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Rbp }; } }
        hloc_constraint CR8 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R8 }; } }
        hloc_constraint CR9 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R9 }; } }
        hloc_constraint CR10 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R10 }; } }
        hloc_constraint CR11 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R11 }; } }
        hloc_constraint CR12 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R12 }; } }
        hloc_constraint CR13 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R13 }; } }
        hloc_constraint CR14 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R14 }; } }
        hloc_constraint CR15 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = R15 }; } }

        hloc_constraint CXmm0 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Specific, specific = Xmm0 }; } }

        hloc_constraint CGpr { get { return new hloc_constraint { constraint = hloc_constraint.c_.AnyOfType, specific = new x86_64_gpr() }; } }
        hloc_constraint CXmm { get { return new hloc_constraint { constraint = hloc_constraint.c_.AnyOfType, specific = new x86_64_xmm() }; } }
        hloc_constraint CMem { get { return new hloc_constraint { constraint = hloc_constraint.c_.AnyOfType, specific = new hardware_stackloc() }; } }
        hloc_constraint COp1 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Operand1 }; } }
        hloc_constraint COp2 { get { return new hloc_constraint { constraint = hloc_constraint.c_.Operand2 }; } }
        hloc_constraint CNone { get { return new hloc_constraint { constraint = hloc_constraint.c_.None }; } }
        hloc_constraint CConst { get { return new hloc_constraint { constraint = hloc_constraint.c_.Immediate }; } }
        hloc_constraint CGprMem { get { return new hloc_constraint { constraint = hloc_constraint.c_.List, specific_list = new List<hloc_constraint> { CGpr, CMem } }; } }
        hloc_constraint CGprPtr { get { return new hloc_constraint { constraint = hloc_constraint.c_.AnyOfType, specific = new hardware_contentsof { base_loc = new x86_64_gpr() } }; } }
        hloc_constraint CStackPtr { get { return new hloc_constraint { constraint = hloc_constraint.c_.AnyOfType, specific = new hardware_addressof { base_loc = new hardware_stackloc() } }; } }
        hloc_constraint CLabel { get { return new hloc_constraint { constraint = hloc_constraint.c_.AnyOfType, specific = new hardware_addressoflabel() }; } }
        hloc_constraint CGprMemPtr { get { return new hloc_constraint { constraint = hloc_constraint.c_.List, specific_list = new List<hloc_constraint> { CGpr, CMem, CGprPtr } }; } }
        hloc_constraint CGprMemConst { get { return new hloc_constraint { constraint = hloc_constraint.c_.List, specific_list = new List<hloc_constraint> { CGpr, CMem, CConst } }; } }
        hloc_constraint CXmmMem { get { return new hloc_constraint { constraint = hloc_constraint.c_.List, specific_list = new List<hloc_constraint> { CXmm, CMem } }; } }
        hloc_constraint CXmmPtrMem { get { return new hloc_constraint { constraint = hloc_constraint.c_.List, specific_list = new List<hloc_constraint> { CXmm, CGprPtr, CMem } }; } }
        hloc_constraint CMemPtr { get { return new hloc_constraint { constraint = hloc_constraint.c_.List, specific_list = new List<hloc_constraint> { CMem, CGprPtr } }; } }
        
        public class x86_64_gpr : hardware_location, IEquatable<hardware_location>
        {
            public enum RegId
            {
                rax = 0, rcx = 1, rdx = 2, rbx = 3, rsp = 4, rbp = 5, rsi = 6, rdi = 7,
                r8 = 8, r9 = 9, r10 = 10, r11 = 11, r12 = 12, r13 = 13, r14 = 14, r15 = 15
            };

            public RegId reg;

            public override string ToString()
            {
                return reg.ToString();
            }

            public bool is_extended { get { if (((int)reg) >= 8) return true; return false; } }
            public byte base_val { get { return (byte)(((int)reg) % 8); } }

            public override bool Equals(hardware_location other)
            {
                if (!(other is x86_64_gpr))
                    return false;
                if (this.reg == ((x86_64_gpr)other).reg)
                    return true;
                return false;
            }
        }

        public class x86_64_xmm : hardware_location, IEquatable<hardware_location>
        {
            public enum XmmId
            {
                xmm0 = 0, xmm1 = 1, xmm2 = 2, xmm3 = 3, xmm4 = 4, xmm5 = 5, xmm6 = 6, xmm7 = 7,
                xmm8 = 8, xmm9 = 9, xmm10 = 10, xmm11 = 11, xmm12 = 12, xmm13 = 13, xmm14 = 14, xmm15 = 15
            };

            public XmmId xmm;

            public override string ToString()
            {
                return xmm.ToString();
            }

            public bool is_extended { get { if (((int)xmm) >= 8) return true; return false; } }
            public int base_val { get { return ((int)xmm) % 8; } }

            public override bool Equals(hardware_location other)
            {
                if (!(other is x86_64_xmm))
                    return false;
                if (this.xmm == ((x86_64_xmm)other).xmm)
                    return true;
                return false;
            }
        }

        protected override hloc_constraint GetConstraintFromSemantic(var_semantic vs)
        {
            if (vs.needs_memloc || vs.needs_vtype)
            {
                hloc_constraint ret = CMem;
                ((hardware_stackloc)ret.specific).size = vs.vtype_size;
                return ret;
            }
            if (vs.needs_float)
                return CXmm;
            if (vs.needs_int32 | vs.needs_int64 | vs.needs_intptr)
                return CGpr;
            throw new NotSupportedException();
        }
       
        protected override void arch_init_opcodes()
        {
            output_opcodes.Add(ThreeAddressCode.Op.nop, new output_opcode
            {
                op = ThreeAddressCode.Op.nop,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_nop,
                        op1 = CNone, op2 = CNone, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.add_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.add_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i4_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i4_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.add_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.add_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i8_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i8_gpr_gprptr,
                        op1 = CGpr, op2 = CGprPtr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.add_i, new output_opcode
            {
                op = ThreeAddressCode.Op.add_i,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i8_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_add_i8_gpr_gprptr,
                        op1 = CGpr, op2 = CGprPtr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.sub_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.sub_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_sub_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_sub_i4_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.sub_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.sub_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_sub_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_sub_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_sub_i8_gpr_gprptr,
                        op1 = CGpr, op2 = CGprPtr, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.sub_i, output_opcodes[ThreeAddressCode.Op.sub_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.and_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.and_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_and_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_and_i4_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.and_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.and_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_and_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_and_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.and_i, new output_opcode
            {
                op = ThreeAddressCode.Op.and_i,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_and_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_and_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.or_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.or_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_or_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_or_i4_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.or_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.or_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_or_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_or_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.or_i, output_opcodes[ThreeAddressCode.Op.or_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.xor_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.xor_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_xor_i48_do1gpr_o2gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_xor_i48_do1gprmem_o2gpr,
                        op1 = CGprMem, op2 = CGpr, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.xor_i8, output_opcodes[ThreeAddressCode.Op.xor_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.xor_i, output_opcodes[ThreeAddressCode.Op.xor_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.cmp_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.cmp_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_cmp_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = CNone
                    },
                    new opcode_choice { code_emitter = x86_64_cmp_i4_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = CNone
                    },
                    new opcode_choice { code_emitter = x86_64_cmp_i4_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.cmp_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.cmp_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_cmp_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = CNone
                    },
                    new opcode_choice { code_emitter = x86_64_cmp_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.cmp_i, new output_opcode
            {
                op = ThreeAddressCode.Op.cmp_i,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_cmp_i8_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = CNone
                    },
                    new opcode_choice { code_emitter = x86_64_cmp_i8_gprmem_gpr,
                        op1 = CGprMem, op2 = CGpr, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.br, new output_opcode
            {
                op = ThreeAddressCode.Op.br,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_br,
                        op1 = CNone, op2 = CNone, result = CNone
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.bb, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.bl, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.beq, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.ba, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.bae, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.bbe, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.bg, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.bge, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.ble, output_opcodes[ThreeAddressCode.Op.br]);
            output_opcodes.Add(ThreeAddressCode.Op.bne, output_opcodes[ThreeAddressCode.Op.br]);
            

            output_opcodes.Add(ThreeAddressCode.Op.ret_void, new output_opcode
            {
                op = ThreeAddressCode.Op.ret_void,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_ret,
                        op1 = CNone, op2 = CNone, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.ret_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.ret_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_ret,
                        op1 = CRax, op2 = CNone, result = CNone
                    }
                },
                recommended_O1 = Rax
            });

            output_opcodes.Add(ThreeAddressCode.Op.ret_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.ret_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_ret,
                        op1 = CRax, op2 = CNone, result = CNone
                    }
                },
                recommended_O1 = Rax
            });

            output_opcodes.Add(ThreeAddressCode.Op.ret_i, new output_opcode
            {
                op = ThreeAddressCode.Op.ret_i,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_ret,
                        op1 = CRax, op2 = CNone, result = CNone
                    }
                },
                recommended_O1 = Rax
            });

            output_opcodes.Add(ThreeAddressCode.Op.ret_r8, new output_opcode
            {
                op = ThreeAddressCode.Op.ret_r8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_ret,
                        op1 = CXmm0, op2 = CNone, result = CNone
                    }
                },
                recommended_O1 = Xmm0
            });

            output_opcodes.Add(ThreeAddressCode.Op.call_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.call_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_call,
                        op1 = new hloc_constraint { constraint = hloc_constraint.c_.List,
                            specific_list = new List<hloc_constraint> { CLabel, CGpr, CGprPtr } },
                        op2 = CNone, result = CRax,
                        clobber_list = new hardware_location[] { Rbx, Rcx, Rdx, Rdi, Rsi, R8, R9, R10, R11, R12, R13, R14, R15,
                            Xmm0, Xmm1, Xmm2, Xmm3, Xmm4, Xmm5, Xmm6, Xmm7, Xmm8, Xmm9, Xmm10, Xmm11, Xmm12, Xmm13, Xmm14, Xmm15 }
                    }
                },
                recommended_R = Rax
            });
            output_opcodes.Add(ThreeAddressCode.Op.call_i8, output_opcodes[ThreeAddressCode.Op.call_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.call_i, output_opcodes[ThreeAddressCode.Op.call_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.call_r4, new output_opcode
            {
                op = ThreeAddressCode.Op.call_r4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_call,
                        op1 = new hloc_constraint { constraint = hloc_constraint.c_.List,
                            specific_list = new List<hloc_constraint> { CLabel, CGpr, CGprPtr }},
                            op2 = CNone, result = CXmm0,
                        clobber_list = new hardware_location[] { Rax, Rbx, Rcx, Rdx, Rdi, Rsi, R8, R9, R10, R11, R12, R13, R14, R15,
                            Xmm1, Xmm2, Xmm3, Xmm4, Xmm5, Xmm6, Xmm7, Xmm8, Xmm9, Xmm10, Xmm11, Xmm12, Xmm13, Xmm14, Xmm15 }
                    }
                },
                recommended_R = Xmm0
            });
            output_opcodes.Add(ThreeAddressCode.Op.call_r8, output_opcodes[ThreeAddressCode.Op.call_r4]);

            output_opcodes.Add(ThreeAddressCode.Op.call_void, new output_opcode
            {
                op = ThreeAddressCode.Op.call_void,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_call,
                        op1 = new hloc_constraint { constraint = hloc_constraint.c_.List,
                            specific_list = new List<hloc_constraint> { CLabel, CGpr, CGprPtr } },
                        op2 = CNone, result = CNone,
                        clobber_list = new hardware_location[] { Rax, Rbx, Rcx, Rdx, Rdi, Rsi, R8, R9, R10, R11, R12, R13, R14, R15,
                            Xmm0, Xmm1, Xmm2, Xmm3, Xmm4, Xmm5, Xmm6, Xmm7, Xmm8, Xmm9, Xmm10, Xmm11, Xmm12, Xmm13, Xmm14, Xmm15 }
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.mul_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.mul_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_mul_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_mul_i4_gpr_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.mul_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.mul_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_mul_i4_gpr_gprmem,
                        op1 = CGpr, op2 = CGprMem, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_mul_i4_gpr_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.mul_i, output_opcodes[ThreeAddressCode.Op.mul_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.mul_un_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.mul_un_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_mul_un_i48_o1rax_o2gprmem,
                        op1 = CRax, op2 = CGprMem, result = CRax, clobber_list = new hardware_location[] { Rdx }
                    }
                },
                recommended_O1 = Rax,
                recommended_R = Rax
            });
            output_opcodes.Add(ThreeAddressCode.Op.mul_un_i, output_opcodes[ThreeAddressCode.Op.mul_un_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.mul_un_i8, output_opcodes[ThreeAddressCode.Op.mul_un_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.div_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.div_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_divrem_srax_s2gprmem_draxrdx,
                        op1 = CRax, op2 = CGprMem, result = CRax, clobber_list = new hardware_location[] { Rdx }
                    }
                },
                recommended_O1 = Rax,
                recommended_R = Rax
            });
            output_opcodes.Add(ThreeAddressCode.Op.div_i, output_opcodes[ThreeAddressCode.Op.div_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.div_i8, output_opcodes[ThreeAddressCode.Op.div_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.div_u, output_opcodes[ThreeAddressCode.Op.div_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.div_u4, output_opcodes[ThreeAddressCode.Op.div_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.div_u8, output_opcodes[ThreeAddressCode.Op.div_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.rem_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.rem_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_divrem_srax_s2gprmem_draxrdx,
                        op1 = CRax, op2 = CGprMem, result = CRdx,
                    }
                },
                recommended_O1 = Rax,
                recommended_R = Rdx
            });
            output_opcodes.Add(ThreeAddressCode.Op.rem_i, output_opcodes[ThreeAddressCode.Op.rem_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.rem_i8, output_opcodes[ThreeAddressCode.Op.rem_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.rem_un_i, output_opcodes[ThreeAddressCode.Op.rem_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.rem_un_i4, output_opcodes[ThreeAddressCode.Op.rem_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.rem_un_i8, output_opcodes[ThreeAddressCode.Op.rem_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.shl_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.shl_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_shl_i4_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_shl_i4_gprmem_cl,
                        op1 = CGprMem, op2 = CRcx, result = COp1
                    }
                },
                recommended_O2 = Rcx
            });
            output_opcodes.Add(ThreeAddressCode.Op.shl_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.shl_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_shl_i8_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_shl_i8_gprmem_cl,
                        op1 = CGprMem, op2 = CRcx, result = COp1
                    }
                },
                recommended_O2 = Rcx
            });
            output_opcodes.Add(ThreeAddressCode.Op.shl_i, output_opcodes[ThreeAddressCode.Op.shl_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.shr_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.shr_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_shr_i4_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_shr_i4_gprmem_cl,
                        op1 = CGprMem, op2 = CRcx, result = COp1
                    }
                },
                recommended_O2 = Rcx
            });
            output_opcodes.Add(ThreeAddressCode.Op.shr_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.shr_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_shr_i8_gprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_shr_i8_gprmem_cl,
                        op1 = CGprMem, op2 = CRcx, result = COp1
                    }
                },
                recommended_O2 = Rcx
            });
            output_opcodes.Add(ThreeAddressCode.Op.shr_i, output_opcodes[ThreeAddressCode.Op.shr_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.shr_un_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.shr_un_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_shr_un_i48_sgprmem_const,
                        op1 = CGprMem, op2 = CConst, result = COp1
                    },
                    new opcode_choice { code_emitter = x86_64_shr_un_i48_sgprmem_cl,
                        op1 = CGprMem, op2 = CRcx, result = COp1
                    }
                },
                recommended_O2 = Rcx
            });
            output_opcodes.Add(ThreeAddressCode.Op.shr_un_i, output_opcodes[ThreeAddressCode.Op.shr_un_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.shr_un_i8, output_opcodes[ThreeAddressCode.Op.shr_un_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i_i4sx, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = null_emit,
                        op1 = CGprMemPtr, op2 = CNone, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_i4sx, output_opcodes[ThreeAddressCode.Op.conv_i_i4sx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_i8sx, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i4i8sx_gpr_gprmem,
                        op1 = CGprMem, op2 = CNone, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_isx, output_opcodes[ThreeAddressCode.Op.conv_i4_i8sx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_i2sx, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i48_i2sx_dgpr_sgprmem,
                        op1 = CGprMem, op2 = CNone, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_i2sx, output_opcodes[ThreeAddressCode.Op.conv_i4_i2sx]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_i2sx, output_opcodes[ThreeAddressCode.Op.conv_i4_i2sx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_i1sx, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i48_i2sx_dgpr_sgprmem,
                        op1 = CGprMem, op2 = CNone, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_i1sx, output_opcodes[ThreeAddressCode.Op.conv_i4_i1sx]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_i1sx, output_opcodes[ThreeAddressCode.Op.conv_i4_i1sx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_u2zx, new output_opcode
            {
                op = ThreeAddressCode.Op.conv_i4_u2zx,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i4u2zx_gpr_gprmem,
                        op1 = CGprMem, op2 = CNone, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_u2zx, output_opcodes[ThreeAddressCode.Op.conv_i4_u2zx]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_u2zx, output_opcodes[ThreeAddressCode.Op.conv_i4_u2zx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_u1zx, new output_opcode
            {
                op = ThreeAddressCode.Op.conv_i4_u1zx,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i4u1zx_gpr_gprmem,
                        op1 = CGprMem, op2 = CNone, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_u1zx, output_opcodes[ThreeAddressCode.Op.conv_i4_u1zx]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_u1zx, output_opcodes[ThreeAddressCode.Op.conv_i4_u1zx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_u8zx, new output_opcode
            {
                op = ThreeAddressCode.Op.conv_i4_u8zx,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i4u8zx_sgprmem_dgprmem,
                        op1 = CGprMem, op2 = CNone, result = CGprMem
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_uzx, output_opcodes[ThreeAddressCode.Op.conv_i4_u8zx]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_u4zx, new output_opcode
            {
                op = ThreeAddressCode.Op.conv_i8_u4zx,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_assign_i4_gpr_gprmem,     // mov to a 32 bit register automatically zero-extends
                        op1 = CGprMemPtr, op2 = CNone, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.assign_i, new output_opcode
            {
                op = ThreeAddressCode.Op.assign_i,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_assign_i8_gpr_gprmem,
                        op1 = CGprMemPtr, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i8_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },                    
                    new opcode_choice { code_emitter = x86_64_assign_i8_gprmem_gpr,
                        op1 = CGpr, op2 = CNone, result = CGprMemPtr
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i8_gprptr_label,
                        op1 = CLabel, op2 = CNone, result = CGprMemPtr, clobber_list = new hardware_location[] { Rdi }
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i8_sptr_dgpr,
                        op1 = CStackPtr, op2 = CNone, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.assign_i8, new output_opcode
            {
                op = ThreeAddressCode.Op.assign_i8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_assign_i8_gpr_gprmem,
                        op1 = CGprMemPtr, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i8_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },                    
                    new opcode_choice { code_emitter = x86_64_assign_i8_gprmem_gpr,
                        op1 = CGpr, op2 = CNone, result = CGprMemPtr
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i8_gprptr_label,
                        op1 = CLabel, op2 = CNone, result = CGprMemPtr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.assign_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.assign_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_assign_i4_gpr_gprmem,
                        op1 = CGprMemPtr, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i4_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },                    
                    new opcode_choice { code_emitter = x86_64_assign_i4_gprmem_const,
                        op1 = CConst, op2 = CNone, result = CGprMemPtr
                    },
                    new opcode_choice { code_emitter = x86_64_assign_i4_gprmem_gpr,
                        op1 = CGpr, op2 = CNone, result = CGprMemPtr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.enter, new output_opcode
            {
                op = ThreeAddressCode.Op.enter,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_enter,
                        op1 = CNone, op2 = CNone, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.seteq, new output_opcode
            {
                op = ThreeAddressCode.Op.seteq,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_set,
                        op1 = CNone, op2 = CNone, result = CGprMem
                    },
                    new opcode_choice { code_emitter = x86_64_set,
                        op1 = CNone, op2 = CNone, result = CGprPtr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.seta, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setae, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setb, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setbe, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setg, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setge, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setl, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setle, output_opcodes[ThreeAddressCode.Op.seteq]);
            output_opcodes.Add(ThreeAddressCode.Op.setne, output_opcodes[ThreeAddressCode.Op.seteq]);

            output_opcodes.Add(ThreeAddressCode.Op.peek_u1, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_u1,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_u1_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_peek_u1_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.peek_u2, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_u2,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_u2_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_peek_u2_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.peek_i1, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_u1,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_i1_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_peek_i1_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.peek_i2, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_u2,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_i2_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_peek_i2_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });


            output_opcodes.Add(ThreeAddressCode.Op.peek_u4, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_u4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_u4_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_peek_u_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.peek_u8, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_u8,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_u8_gpr_const,
                        op1 = CConst, op2 = CNone, result = CGpr
                    },
                    new opcode_choice { code_emitter = x86_64_peek_u_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.peek_u, output_opcodes[ThreeAddressCode.Op.peek_u8]);

            output_opcodes.Add(ThreeAddressCode.Op.peek_r4, new output_opcode
            {
                op = ThreeAddressCode.Op.peek_r4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_r48_dxmm_sgpr,
                        op1 = CGpr, op2 = CNone, result = CXmm
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.peek_r8, output_opcodes[ThreeAddressCode.Op.peek_r4]);

            output_opcodes.Add(ThreeAddressCode.Op.ldobj_i4, new output_opcode
            {
                op = ThreeAddressCode.Op.ldobj_i4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_peek_u_gpr_gpr,
                        op1 = CGpr, op2 = CNone, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.ldobj_i, output_opcodes[ThreeAddressCode.Op.ldobj_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.ldobj_i8, output_opcodes[ThreeAddressCode.Op.ldobj_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.poke_u1, new output_opcode
            {
                op = ThreeAddressCode.Op.poke_u1,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_poke_u1_gprconst_gprmemconst,
                        op1 = new hloc_constraint { constraint = hloc_constraint.c_.List,
                            specific_list = new List<hloc_constraint> { CGpr, CMem, CConst } },
                        op2 = new hloc_constraint { constraint = hloc_constraint.c_.List,
                            specific_list = new List<hloc_constraint> { CGpr, CConst} }, result = CNone,
                        clobber_list = new hardware_location[] { Rdi }
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.poke_u2, output_opcodes[ThreeAddressCode.Op.poke_u1]);
            output_opcodes.Add(ThreeAddressCode.Op.poke_u4, output_opcodes[ThreeAddressCode.Op.poke_u1]);
            output_opcodes.Add(ThreeAddressCode.Op.poke_u8, output_opcodes[ThreeAddressCode.Op.poke_u1]);
            output_opcodes.Add(ThreeAddressCode.Op.poke_u, output_opcodes[ThreeAddressCode.Op.poke_u1]);

            output_opcodes.Add(ThreeAddressCode.Op.poke_r4, new output_opcode
            {
                op = ThreeAddressCode.Op.poke_r4,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_poke_r48_o1gpr_o2xmm,
                        op1 = CGpr, op2 = CXmm, result = CNone
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.poke_r8, output_opcodes[ThreeAddressCode.Op.poke_r4]);

            output_opcodes.Add(ThreeAddressCode.Op.portout_u2_u1, new output_opcode
            {
                op = ThreeAddressCode.Op.portout_u2_u1,
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_portout_u2_u1_dx_rax,
                        op1 = CRdx, op2 = CRax, result = CNone
                    },
                    new opcode_choice { code_emitter = x86_64_portout_u2_u1_const_rax,
                        op1 = CConst, op2 = CRax, result = CNone
                    }
                },
                recommended_O1 = Rdx
            });

            output_opcodes.Add(ThreeAddressCode.Op.throw_, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_throw,
                        op1 = CGprMemConst, op2 = CNone, result = CNone
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.throweq, output_opcodes[ThreeAddressCode.Op.throw_]);
            output_opcodes.Add(ThreeAddressCode.Op.throwne, output_opcodes[ThreeAddressCode.Op.throw_]);
            output_opcodes.Add(ThreeAddressCode.Op.throw_ovf, output_opcodes[ThreeAddressCode.Op.throw_]);
            output_opcodes.Add(ThreeAddressCode.Op.throw_ovf_un, output_opcodes[ThreeAddressCode.Op.throw_]);

            output_opcodes.Add(ThreeAddressCode.Op.cmp_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_cmp_r8_xmm_xmmmem,
                        op1 = CXmm, op2 = CXmmMem, result = CNone
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.assign_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_assign_r8_xmm_xmmmem,
                        op1 = CXmmPtrMem, op2 = CNone, result = CXmm
                    },
                    new opcode_choice { code_emitter = x86_64_assign_r8_xmmmem_xmm,
                        op1 = CXmm, op2 = CNone, result = CXmmPtrMem
                    },
                    new opcode_choice { code_emitter = x86_64_assign_r48_simm_dxmm,
                        op1 = CConst, op2 = CNone, result = CXmm
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_r4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_i48r48_gprmem_xmm,
                        op1 = CGprMem, op2 = CNone, result = CXmm
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_i4_r8, output_opcodes[ThreeAddressCode.Op.conv_i4_r4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_r4, output_opcodes[ThreeAddressCode.Op.conv_i4_r4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_r8, output_opcodes[ThreeAddressCode.Op.conv_i4_r4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_r4, output_opcodes[ThreeAddressCode.Op.conv_i4_r4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_r8, output_opcodes[ThreeAddressCode.Op.conv_i4_r4]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_r4_i4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_r48i48_dgpr_sxmmmem,
                        op1 = CXmmPtrMem, op2 = CNone, result = CGpr
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.conv_r4_i8, output_opcodes[ThreeAddressCode.Op.conv_r4_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_r4_i, output_opcodes[ThreeAddressCode.Op.conv_r4_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_r8_i4, output_opcodes[ThreeAddressCode.Op.conv_r4_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_r8_i8, output_opcodes[ThreeAddressCode.Op.conv_r4_i4]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_r8_i, output_opcodes[ThreeAddressCode.Op.conv_r4_i4]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_r4_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_r4r8_dxmm_sxmmmem,
                        op1 = CXmmPtrMem, op2 = CNone, result = CXmm
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.conv_r8_r4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_conv_r8r4_dxmm_sxmmmem,
                        op1 = CXmmPtrMem, op2 = CNone, result = CXmm
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.sqrt_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_sqrt_r8_dxmm_sxmmmem,
                        op1 = CXmmPtrMem, op2 = CNone, result = CXmm
                    }
                }
            });

            output_opcodes.Add(ThreeAddressCode.Op.assign_vt, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_memcpy_srsi_drdi,
                        op1 = CRsi, op2 = CNone, result = CRdi, clobber_list = new hardware_location[] { Rcx }
                    },
                    new opcode_choice { code_emitter = x86_64_memcpy_srsi_dmem,
                        op1 = CRsi, op2 = CNone, result = CMemPtr, clobber_list = new hardware_location[] { Rdi, Rcx }
                    },
                    new opcode_choice { code_emitter = x86_64_memcpy_smem_drdi,
                        op1 = CMemPtr, op2 = CNone, result = CRdi, clobber_list = new hardware_location[] { Rsi, Rcx }
                    },
                    new opcode_choice { code_emitter = x86_64_memcpy_smem_dmem,
                        op1 = CMemPtr, op2 = CNone, result = CMemPtr, clobber_list = new hardware_location[] { Rsi, Rdi, Rcx }
                    }
                },
                recommended_O1 = Rsi,
                recommended_R = Rdi
            });
            output_opcodes.Add(ThreeAddressCode.Op.ldobj_vt, output_opcodes[ThreeAddressCode.Op.assign_vt]);

            output_opcodes.Add(ThreeAddressCode.Op.stobj_vt, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_stobj_vt,
                        op1 = CRdi, op2 = CMemPtr, result = CNone, clobber_list = new hardware_location[] { Rcx, Rsi }
                    },
                    new opcode_choice { code_emitter = x86_64_stobj_vt,
                        op1 = CRdi, op2 = CRsi, result = CNone, clobber_list = new hardware_location[] { Rcx }
                    }
                },
                recommended_O1 = Rdi,
                recommended_O2 = Rsi
            });
            
            output_opcodes.Add(ThreeAddressCode.Op.ldobj_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_ldobj_r8_sgprptr_dxmm,
                        op1 = CGpr, op2 = CNone, result = CXmm
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.ldobj_r4, output_opcodes[ThreeAddressCode.Op.ldobj_r8]);

            output_opcodes.Add(ThreeAddressCode.Op.neg_i8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_negi48_sgprmem_dop1,
                        op1 = CGprMem, op2 = CNone, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.neg_i4, output_opcodes[ThreeAddressCode.Op.neg_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.neg_i, output_opcodes[ThreeAddressCode.Op.neg_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.not_i8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_noti48_sgprmem_dop1,
                        op1 = CGprMem, op2 = CNone, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.not_i4, output_opcodes[ThreeAddressCode.Op.not_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.not_i, output_opcodes[ThreeAddressCode.Op.not_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.alloca_i, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_alloca_i48_sgpr,
                        op1 = CGpr, op2 = CNone, result = CGprMem
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.alloca_i4, output_opcodes[ThreeAddressCode.Op.alloca_i]);

            output_opcodes.Add(ThreeAddressCode.Op.conv_i_i8sx, output_opcodes[ThreeAddressCode.Op.assign_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_isx, output_opcodes[ThreeAddressCode.Op.assign_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_u8zx, output_opcodes[ThreeAddressCode.Op.assign_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i_uzx, output_opcodes[ThreeAddressCode.Op.assign_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_isx, output_opcodes[ThreeAddressCode.Op.assign_i8]);
            output_opcodes.Add(ThreeAddressCode.Op.conv_i8_uzx, output_opcodes[ThreeAddressCode.Op.assign_i8]);

            output_opcodes.Add(ThreeAddressCode.Op.zeromem, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_zeromem_o1rdi_o2const,
                        op1 = CRdi, op2 = CConst, result = CNone, clobber_list = new hardware_location[] { Rax, Rcx }
                    }
                },
                recommended_O1 = Rdi
            });

            output_opcodes.Add(ThreeAddressCode.Op.add_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_r8_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.add_r4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_r4_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.sub_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_sub_r8_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.sub_r4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_sub_r4_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.mul_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_mul_r8_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.mul_r4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_mul_r4_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.div_r8, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_r8_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });
            output_opcodes.Add(ThreeAddressCode.Op.div_r4, new output_opcode
            {
                opcode_choice = new opcode_choice[] {
                    new opcode_choice { code_emitter = x86_64_add_r4_ds1xmm_s2_xmmmem,
                        op1 = CXmm, op2 = CXmmPtrMem, result = COp1
                    }
                }
            });

        }

        IEnumerable<OutputBlock> x86_64_enter(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return new List<OutputBlock>();
            /*CodeBlock lvar_stack = new CodeBlock { Code = new List<byte>() };
            ((x86_64_RegisterAllocator)state.reg_alloc).set_up_lvar_stack = lvar_stack;
            return new List<OutputBlock> { new CodeBlock { Code = new List<byte>
            { 0x55, 0x48, 0x89, 0xe5 } }, lvar_stack };     // push rbp; mov rbp, rsp, then space to do an sub rsp, lvar_stack_size */
        }

        IEnumerable<OutputBlock> x86_64_ret(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // set up the stack at the beginning of the procedure, if necessary
            x86_64_RegisterAllocator ralloc = state.reg_alloc as x86_64_RegisterAllocator;
            x86_64_RegisterAllocator.x86_64_LocalVarHardwareAllocator lvar_alloc = ralloc.GetLocalVarAllocator() as x86_64_RegisterAllocator.x86_64_LocalVarHardwareAllocator;
            if (lvar_alloc.stack_alloc.GetStackSize() != 0)
                ralloc.set_up_lvar_stack.Code = new List<byte>(EncOpcode(0, Rsp, 3, true, lvar_alloc.stack_alloc.GetStackSize(), 0x81));    // add rsp, size (which is negative)
            
            // leave, ret
            return new List<OutputBlock> { new CodeBlock { Code = new List<byte> { 0xc9, 0xc3 } } };
        }

        IEnumerable<OutputBlock> x86_64_alloca_i48_sgpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Allocate some space on the stack
            /* Do: sub rsp, size; mov dest, rsp
             * This works because all other local variables are addressed in reference to rbp
             */

            return OBList(EncOpcode(Rsp, op1.hardware_loc, 3, true, 0, 0x2b),
                EncOpcode(result.hardware_loc, Rsp, 3, true, 0, 0x89));
        }

        IEnumerable<OutputBlock> x86_64_nop(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            if (emitNop)
                return OBList(new byte[] { 0x90 });
            else
                return new List<OutputBlock>();
        }
    }
}
